use libc::{c_char, c_int, c_void, c_uint, size_t};
// use core::slice::SlicePattern;
// use std::borrow::BorrowMut;
use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};
use std::io::SeekFrom;
use std::io::prelude::*;
// use lazy_static::lazy_static;
use std::cell::RefCell;
use structopt::StructOpt;
use std::error::Error;

extern crate lazy_static; 

const LFS_O_RDONLY: i32 = 1;
const LFS_O_WRONLY: i32 = 2;
const LFS_O_RDWR: i32 = 3;
const LFS_O_CREAT: i32 = 0x0100;
const LFS_O_EXCL: i32 = 0x0200;
const LFS_O_TRUNC: i32 = 0x0400;
const LFS_O_APPEND: i32 = 0x0800;

#[repr(C, align(4))]
struct lfs_info {
    typ: u8,
    size: u32,
    name: [u8; 256]
}

#[repr(C, align(4))]
struct lfs_attr {
    typ: u8,
    buffer: *mut c_void,
    size: u32
}

#[repr(C, align(4))]
struct lfs_file_config {
    buffer: *mut c_void,
    attrs: *mut lfs_attr,
    attr_count: u32
}

#[repr(C, align(4))]
struct lfs_cache_t {
    block: u32,
    off: u32,
    size: u32,
    buffer: *mut u8
}

#[repr(C, align(4))]
struct lfs_mdir_t {
    pair: [u32; 2],
    rev: u32,
    off: u32,
    etag: u32,
    count: u16,
    erased: bool,
    split: bool,
    tail: [u32; 2]
}

#[repr(C, align(4))]
struct  lfs_dir_t {
    next: *mut lfs_dir_t,
    id: u16,
    typ: u8,
    m: lfs_mdir_t,
    pos: u32,
    head: [u32; 2]
}

#[repr(C, align(4))]
struct lfs_ctz {
    head: u32,
    size: u32
}

#[repr(C, align(4))]
struct lfs_file_t {
    next: *mut lfs_file_t,
    id: u16,
    typ: u8,
    m: lfs_mdir_t,
    ctz: lfs_ctz,
    flags: u32,
    pos: u32,
    block: u32,
    off: u32,
    cache: lfs_cache_t,
    cfg: *const lfs_file_config
}

#[repr(C, align(4))]
struct lfs_mlist {
    next: *mut lfs_mlist,
    id: u16,
    typ: u8,
    m: lfs_mdir_t
}

#[repr(C, align(4))]
struct lfs_gstate_t {
    tag: u32,
    pair: [u32; 2]
}

#[repr(C, align(4))]
struct lfs_free {
    off: u32,
    size: u32,
    i: u32,
    ack: u32,
    buffer: *mut u32
}

#[repr(C, align(4))]
struct lfs_t {
    rcache: lfs_cache_t,
    pcache: lfs_cache_t,
    root: [u32; 2],
    mlist: *mut lfs_mlist,
    seed: u32,

    gstate: lfs_gstate_t,
    gdisk: lfs_gstate_t,
    gdelta: lfs_gstate_t,

    free: lfs_free,

    cfg: *const lfs_config,
    name_max: u32,
    file_max: u32,
    attr_max: u32
}

#[repr(C, align(4))]
struct lfs_config {
    context: *mut c_void,
    read: fn(c: &lfs_config, block: u32, off: u32, buffer: *mut c_void, size: u32) -> i32,
    prog: fn(c: &lfs_config, block: u32, off: u32, buffer: *const c_void, size: u32) -> i32,
    erase: fn(c: &lfs_config, block: u32) -> i32,
    sync: fn(c: &lfs_config) -> i32,
    read_size: u32,
    prog_size: u32,
    block_size: u32,
    block_count: u32,
    block_cycles: i32,
    cache_size: u32,
    lookahead_size: u32,
    read_buffer: *mut c_void,
    prog_buffer: *mut c_void,
    lookahead_buffer: *mut c_void,
    name_max: u32,
    file_max: u32,
    attr_max: u32,
    metadata_max: u32
}

#[link(name="lfs")]
extern "C" {
    fn lfs_format(lfs: &mut lfs_t, config: &mut lfs_config) -> i32;
    fn lfs_mount(lfs: &mut lfs_t, config: &mut lfs_config) -> i32;
    fn lfs_unmount(lfs: &mut lfs_t) -> i32;
    fn lfs_dir_open(lfs: &mut lfs_t, dir: &mut lfs_dir_t, path: *const libc::c_char) -> i32;
    fn lfs_dir_read(lfs: &mut lfs_t, dir: &mut lfs_dir_t, info: &mut lfs_info) -> i32;
    fn lfs_dir_close(lfs: &mut lfs_t, dir: &mut lfs_dir_t) -> i32;
    fn lfs_file_open(lfs: &mut lfs_t, file: &mut lfs_file_t, path: *const libc::c_char, flags: i32) -> i32;
    fn lfs_file_read(lfs: &mut lfs_t, file: &mut lfs_file_t, buffer: *mut libc::c_void, size: u32) -> i32;
    fn lfs_file_write(lfs: &mut lfs_t, file: &mut lfs_file_t, buffer: *const libc::c_void, size: u32) -> i32;
    fn lfs_file_close(lfs: &mut lfs_t, file: &mut lfs_file_t) -> i32;
    fn lfs_mkdir(lfs: &mut lfs_t, path: *const libc::c_char) -> i32;
}

thread_local! {
static ROOTFS:RefCell<String> = RefCell::new(String::from("rootfs.lfs"));
static IMG_FILE:RefCell<File> = RefCell::new(OpenOptions::new()
                                                            .write(true)
                                                            .create(true)
                                                            .read(true)
                                                            .open("rootfs.lfs").unwrap());
}

static mut VERBOSE: bool = false;

#[no_mangle]
fn lfs_read(c: &lfs_config, block: u32, off: u32, buffer: *mut c_void, size: u32) -> i32
{
    let mut buf = Vec::new();
    buf.resize(size as usize, 0);

    IMG_FILE.with(|m_file|{
        let mut fil = m_file.borrow_mut();
        fil.seek(SeekFrom::Start((block as u64 * c.block_size as u64) + off as u64)).unwrap();
        fil.read(buf.as_mut_slice()).unwrap();
        unsafe { std::ptr::copy_nonoverlapping(buf.as_mut_slice().as_ptr(), buffer as *mut u8, size as usize) };
    });

    // println!("lfs_read: {}", buf.len());
    return 0;
}

#[no_mangle]
fn lfs_prog(c: &lfs_config, block: u32, off: u32, buffer: *const c_void, size: u32) -> i32
{
    // println!("lfs_prog");
    let buf = unsafe {
        std::slice::from_raw_parts(buffer as *const u8, size as usize)
    };
    IMG_FILE.with(|m_file: &RefCell<File>|{
        let mut fil = m_file.borrow_mut();
        fil.seek(SeekFrom::Start((block as u64 * c.block_size as u64) + off as u64)).unwrap();
        fil.write(buf).unwrap();
    });
    return 0;
}

#[no_mangle]
fn lfs_erase(c: &lfs_config, block: u32) -> i32
{
    // println!("lfs_erase");
    let mut buf = Vec::new();
    buf.resize(c.block_size as usize, 0xFF);

    IMG_FILE.with(|m_file|{
        let mut fil = m_file.borrow_mut();
        fil.seek(SeekFrom::Start(block as u64 * c.block_size as u64)).unwrap();
        fil.write(&buf).unwrap();
    });

    return 0;
}

#[no_mangle]
fn lfs_sync(_c: &lfs_config) -> i32
{
    // println!("lfs_sync");

    IMG_FILE.with(|m_file|{
        let mut fil = m_file.borrow_mut();
        fil.flush().unwrap();
    });

    return 0;
}

impl lfs_config {
    fn new() -> Self {
        Self {
            context: 0 as *mut c_void,
            read: lfs_read,
            prog: lfs_prog,
            erase: lfs_erase,
            sync: lfs_sync,
            read_size: 0,
            prog_size: 0,
            block_size: 0,
            block_count: 0,
            block_cycles: 0,
            cache_size: 0,
            lookahead_size: 0,
            read_buffer: 0 as *mut c_void,
            prog_buffer: 0 as *mut c_void,
            lookahead_buffer: 0 as *mut c_void,
            name_max: 0,
            file_max: 0,
            attr_max: 0,
            metadata_max: 0,
        }
    }
}

fn create_dir(img_lfs: &mut lfs_t, dir: &str) -> i32
{
    let cur_dir = std::ffi::CString::new(dir).unwrap();
    let err = unsafe { lfs_mkdir(img_lfs, cur_dir.as_ptr()) };
    if err < 0 {
        // println!("lfs_mkdir err: {}", err);
        return -1;
    }

    return 0;
}

fn create_file(img_lfs: &mut lfs_t, src: &PathBuf, dst: &str) -> i32
{
    let ret = 0;
    let mut dat = Vec::new();
    dat.resize(1024, 0);

    let mut src_fd = File::open(src).unwrap();
    let mut dst_fd: lfs_file_t = unsafe { std::mem::zeroed() };

    let cur_file = std::ffi::CString::new(dst).unwrap();
    let err = unsafe {
        lfs_file_open(img_lfs, &mut dst_fd, cur_file.as_ptr(), LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) 
    }; 

    loop {
        let rd = src_fd.read(dat.as_mut_slice()).unwrap();
        if rd > 0 {
            unsafe {
                lfs_file_write(img_lfs, &mut dst_fd, dat.as_ptr() as *const c_void, rd as u32)
            };
        } else {
            break;
        }
    }

    let err = unsafe {
        lfs_file_close(img_lfs, &mut dst_fd)
    };

    return ret;
}

fn mklfs(img_lfs: &mut lfs_t, root: &str, dst: &str) -> i32
{
    let ret = 0;
    let cur_dir = std::path::PathBuf::from(format!("{}{}", root, dst));
    println!("mklfs: root({}), localdir({}), lfsdir({})", root, cur_dir.display(), dst);
    for entry in std::fs::read_dir(cur_dir).unwrap() {
        let path = entry.unwrap().path();
        let metadata = std::fs::metadata(path.clone()).unwrap();

        if metadata.is_dir() {
            let sub_dir = path.file_name().unwrap().to_os_string().into_string().unwrap();
            let dst_dir = format!("{}/{}", dst, sub_dir);
            println!("    DIR {} {}", path.display(), dst_dir);
            create_dir(img_lfs, dst_dir.as_str());
            
            // let path_str = path.as_os_str().to_os_string().into_string().unwrap();
            // let sub_dir = path_str.as_str();
            // println!("sub dir: {}", sub_dir);
            mklfs(img_lfs, root, dst_dir.as_str());
        } else if metadata.is_file() {
            let file_name = path.file_name().unwrap().to_os_string().into_string().unwrap();
            let dst_file = format!("{}/{}", dst, file_name.as_str());
            println!("    FILE {} {:?}", path.display(), dst_file);
            create_file(img_lfs, &path, dst_file.as_str());
        }
    }

    return ret;
}

fn tree(img_lfs: &mut lfs_t, dir: &str, level: i32)
{
    let mut fd_dir: lfs_dir_t = unsafe { std::mem::zeroed() };
    let mut dir_info: lfs_info = unsafe { std::mem::zeroed() };

    let cur_dir = std::ffi::CString::new(dir).unwrap();
    let err = unsafe { lfs_dir_open(img_lfs, &mut fd_dir, cur_dir.as_ptr()) };
    if err < 0 {
        // println!("lfs_dir_open err: {}", err);
    } else {
        loop {
            let err = unsafe { lfs_dir_read(img_lfs, &mut fd_dir, &mut dir_info) };
            if err <= 0 {
                // println!("lfs_dir_read err: {}", err);
                break;
            }
            let mut name = String::from("");
            let n = &dir_info.name[..];
            let n = std::str::from_utf8(n).unwrap();
            let ind = n.find('\0');
            match ind {
                Some(i) => {name = String::from(&n[..i]);},
                None => {}
            }
            if String::from(".") == name || String::from("..") == name {
                continue;
            }
            let numb: usize = level as usize * 4;
            print!("{:<width$}", "", width = numb);
            if dir_info.typ == 0x2 {
                println!("{} {}", "DIR ", name);

                let sub_path = format!("{}{}{}", dir, "/", name);
                tree(img_lfs, sub_path.as_str(), level + 1);
            } else {
                println!("{} {}", "FILE", name);
            }
        }

        let err = unsafe { lfs_dir_close(img_lfs, &mut fd_dir) };
    }
}

fn cat(img_lfs: &mut lfs_t, file: &str)
{
    let mut fd_file: lfs_file_t = unsafe { std::mem::zeroed() };
    let mut buff: [u8; 1024] = [0; 1024];

    let cur_file = std::ffi::CString::new(file).unwrap();
    let err = unsafe { lfs_file_open(img_lfs, &mut fd_file, cur_file.as_ptr(), LFS_O_RDONLY) };
    if err < 0 {
        // println!("lfs_dir_open err: {}", err);
    } else {
        loop {
            let err = unsafe { lfs_file_read(img_lfs, &mut fd_file, buff.as_mut_ptr() as *mut c_void, 1024)};
            if err <= 0 {
                // println!("lfs_dir_read err: {}", err);
                break;
            }
            let data = &buff[..err as usize];
            println!("{}", std::str::from_utf8(data).unwrap().to_string());
        }
        let err = unsafe { lfs_file_close(img_lfs, &mut fd_file) };
    }
}

#[derive(Debug, StructOpt)]
#[structopt(name = "mklfs", version = "2.0.0", about = "Make LFS file system Image from an existing directory tree.")]
struct Arguments {
    /// Build file system from directory root
    #[structopt(short, long)]
    root: PathBuf,

    /// Output file, <rootfs.lfs> default
    #[structopt(short, long)]
    output: Option<String>,

    /// Size of an erasable block in bytes
    #[structopt(short, long)]
    block: u32,

    /// Maximum erasable block count
    #[structopt(short = "C", long)]
    count: u32,

    /// Size of block caches in bytes
    #[structopt(short = "c", long)]
    cache: u32,

    /// Minimum I/O size in bytes
    #[structopt(short, long)]
    mio: u32,

    /// Enable verbose output
    #[structopt(short, long)]
    verbose: bool
}

fn main() -> Result<(), Box<dyn Error>> {
    println!("Hello, Lfs!");

    let mut img_lfs: lfs_t = unsafe { std::mem::zeroed() };

    let mut lfs_cfg = lfs_config::new();
    lfs_cfg.block_size  = 4096;
    lfs_cfg.read_size   = 256;
    lfs_cfg.prog_size   = 256;
    lfs_cfg.block_count = 256;
    lfs_cfg.block_cycles = -1;
    lfs_cfg.cache_size  = 256;
    lfs_cfg.lookahead_size = 128;

    let mut rootfs = "rootfs";
    let mut ofile = String::from("");
    
    let args = Arguments::from_args();
    // println!("{:?} {:?} {} {} {} {} {}", args.root, args.output, args.block, args.count, args.cache, args.mio, args.verbose);

    let root = args.root.clone().into_os_string().into_string().unwrap();
    rootfs = root.as_str();
    ofile = match args.output {
        Some(o) => o,
        _ => String::from("rootfs.lfs"),
    };
    lfs_cfg.block_size = args.block;
    lfs_cfg.read_size = args.mio;
    lfs_cfg.prog_size = args.mio;
    lfs_cfg.block_count = args.count;
    lfs_cfg.cache_size = args.cache;
    unsafe { VERBOSE = args.verbose; }

// format lfs image
    let ret = unsafe { lfs_format(&mut img_lfs, &mut lfs_cfg) };

    let ret = unsafe { lfs_mount(&mut img_lfs, &mut lfs_cfg) };
    mklfs(&mut img_lfs, rootfs, "");

    let ret = unsafe { lfs_unmount(&mut img_lfs) };
    println!("unmount: {}", ret);

// tree lfs 
    let ret = unsafe { lfs_mount(&mut img_lfs, &mut lfs_cfg) };

    println!("mount: {}", ret);

    tree(&mut img_lfs, "/", 0);
    cat(&mut img_lfs, "/Makefile");

    let ret = unsafe { lfs_unmount(&mut img_lfs) };
    println!("unmount: {}", ret);

    return Ok(());
}
